package com.mtconnect.util;

import com.mtconnect.common.Constant;
import com.mtconnect.exception.ResourceLoadException;
import org.dom4j.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;

import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.*;

/**
 * Created by Administrator on 2017/7/21.
 */
public class ResourceLoad {

    private static final Logger LOGGER = LoggerFactory.getLogger(ResourceLoad.class);

    public static List<Document> DEVICES;
    public static List<Document> ASSETS;
    public static final String RESOURCE_NAME = "config.properties";
    public static final String DEVICE_XSD = Constant.DEVICE_PATH;
    public static final String ASSETS_XSD = Constant.ASSET_PATH;
    public static final String MTCONNECT_DEVICE = "MTConnectDevices.xml";
    public static final String MTCONNECT_ASSET = "MTConnectAssets.xml";
    public static final String MTCONNECT_ERROT = "MTConnectError.xml";
    public static final String MTCONNECT_STREAMS = "MTConnectStreams.xml";
    public static final Properties CONFIG = getConfig();
    public static final List<Node> DATA_ITEM_ELEMENT = getAllDataItemElement();
    public static final Map<String, String> TYPE_AND_CATEGORY = getTypeAndCategoryByDataItem();
    public static final Document ALL_DEVICE = getAllDevice();
    public static final Document ALL_ASSETS = getAllAssets();

    /**
     * 加载properties类型资源
     *
     * @param path 文件的绝对路径，如果为null则会加载默认的资源文件。
     * @return properties资源对象
     * @throws ResourceLoadException 读取文件错误抛出此异常
     */
    public static Properties getResources(String path) throws ResourceLoadException {
        LOGGER.debug("param is {}", path);
        Properties properties = new Properties();
        try {
            if (null != path) {
                File file = new File(path);
                if (file.exists()) {
                    properties.load(new FileInputStream(file));
                    return properties;
                }
            }
            properties.load(ResourceLoad.class.getClassLoader().getResourceAsStream(RESOURCE_NAME));
        } catch (IOException e) {
            throw new ResourceLoadException(e);
        }
        return properties;
    }

    /**
     * 加载所有的device资源
     *
     * @param path 指定加载路径，可以为null，为null则加载配置文件中的默认路径
     * @return dom对象集合
     * @throws ResourceLoadException 资源读取出现异常
     */
    public static List<Document> getDocuments(String path) throws ResourceLoadException {
        LOGGER.debug("param is {}", path);
        File file;
        List<Document> list = new ArrayList<>();
        if (null != path) {
            file = new File(path);
        } else {
            throw new ResourceLoadException("path is null");
        }
        if (file.isDirectory()) {
            File[] files = file.listFiles((dir, name) -> name.endsWith("xml"));
            for (File file1 : files) {
                try {
                    list.add(XMLUtil.loadXML(file1));
                } catch (DocumentException e) {
                    LOGGER.debug("file name is {}", file1.getName());
                    throw new ResourceLoadException(e);
                }
            }
        } else {
            if (file.getName().endsWith("xml")) {
                try {
                    list.add(XMLUtil.loadXML(path));
                } catch (DocumentException e) {
                    LOGGER.debug("file name is {}", file.getName());
                    throw new ResourceLoadException(e);
                }
            } else {
                throw new ResourceLoadException("path is not dir and end with xml file");
            }
        }
        return list;
    }

    /**
     * 验证xml文档的正确性。并返回通过验证的文档。
     *
     * @param documents 需要验证的文档集合
     * @param url       规则路径，http规则
     * @return 返回通过验证的xml文档
     */
    public static List<Document> validatorXML(List<Document> documents, String url) throws ResourceLoadException {
        LOGGER.debug("documents: {},url : {}", documents);
        try {
            URL url1 = new URL(url);
            for (Iterator<Document> iterator = documents.iterator(); iterator.hasNext(); ) {
                if (!XMLUtil.validatorXML(iterator.next(), url1)) {
                    LOGGER.warn("Device XML file is no pass to validator");
                    iterator.remove();
                }
            }
            return documents;
        } catch (ParserConfigurationException e) {
            throw new ResourceLoadException(e);
        } catch (IOException e) {
            throw new ResourceLoadException(e);
        } catch (SAXException e) {
            throw new ResourceLoadException(e);
        }
    }

    /**
     * 取得所有的DataItem元素
     *
     * @return 找到的DataItem元素的集合
     */
    public static List<Node> getAllDataItemElement() {
        List<Node> list = null;
        try {
            DEVICES = validatorXML(getDocuments(getResources(null).getProperty("devicePath")), DEVICE_XSD);
            ASSETS = validatorXML(getDocuments(getResources(null).getProperty("assetsPath")), ASSETS_XSD);
        } catch (ResourceLoadException e) {
            LOGGER.error("device files load failed", e);
        }
        if (Objects.nonNull(DEVICES)) {
            list = new ArrayList<>();

            XPath xPath = DocumentHelper.createXPath("//d:DataItem");
            Map<String, String> map = new HashMap<>();
            map.put("d", "urn:mtconnect.org:MTConnectDevices:1.3");
            xPath.setNamespaceURIs(map);
            for (Document document : DEVICES) {
                List<Node> list1 = xPath.selectNodes(document);
                list.addAll(list1);
            }
        }
        return list;
    }

    /**
     * 取得DataItem元素中所有的type以及category属性。并以category属性的首字母重新命名type元素，作返回Map对象的键，值为category。
     * 例：
     * category="EVENT"
     * type="SING"
     * 新的type名称为：E_SING
     * 注：不会改变当前元素的type属性
     *
     * @return 一个以新type为键，值为category属性的Map对象。
     */
    public static Map<String, String> getTypeAndCategoryByDataItem() {
        Map<String, String> map = new TreeMap<>();
        for (Node node : DATA_ITEM_ELEMENT) {
            Element e = (Element) node;
            String type = e.attribute("type").getValue();
            String category = e.attribute("category").getValue();
            if (category.startsWith("C")) {
                type = Constant.PRE_CONDITION + type;
            } else if (category.startsWith("E")) {
                type = Constant.PRE_EVENT + type;
            } else {
                type = Constant.PRE_SAMPLE + type;
            }
            map.put(type, category);
        }
        return map;
    }

    /**
     * 加载类路径下的xml文档资源，并返回对象
     *
     * @param name 类路径下资源名称
     * @return dom4j中的Document对象，或者是 null（当资源加载出错时）
     */
    private static Document getRoot(String name) {
        try {
            InputStream stream = ResourceLoad.class.getClassLoader().getResourceAsStream(name);
            return XMLUtil.loadXML(stream);
        } catch (DocumentException e) {
            LOGGER.error("load MTConnectDevices.xml error", e);
            return null;
        }
    }

    /**
     * 获取项目初始化配置文件信息
     *
     * @return properties资源对象
     */
    private static Properties getConfig() {
        try {
            return getResources(null);
        } catch (ResourceLoadException e) {
            LOGGER.error("resources load error", e);
            return null;
        }
    }

    public static Document getDeviceRoot() {
        return getRoot(MTCONNECT_DEVICE);
    }

    public static Document getAssetsRoot() {
        return getRoot(MTCONNECT_ASSET);
    }

    public static Document getErrorRoot() {
        return getRoot(MTCONNECT_ERROT);
    }

    public static Document getStreamsRoot() {
        return getRoot(MTCONNECT_STREAMS);
    }

    /**
     * 将分散的设备信息整合到一个对象中
     *
     * @return 整合完成的设备信息对象
     */
    private static Document getAllDevice() {
        Document devices = DocumentHelper.createDocument(DocumentHelper.createElement("Devices"));
        Element rootElement = devices.getRootElement();
        for (Document document : DEVICES) {
            List<Element> elements = document.getRootElement().element("Devices").elements();
            for (Element element : elements) {
                rootElement.add(element.createCopy());
            }
        }
        return devices;
    }

    private static Document getAllAssets() {
        Document devices = DocumentHelper.createDocument(DocumentHelper.createElement("Assets"));
        Element rootElement = devices.getRootElement();
        for (Document document : ASSETS) {
            List<Element> elements = document.getRootElement().element("Assets").elements();
            for (Element element : elements) {
                rootElement.add(element.createCopy());
            }
        }
        return devices;
    }

}
